using Sandia.MVCFramework.Plugin;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace Sandia.MVCFramework.Common
{
    /// <summary>
    /// A delegate used for events related to selecting and deselecting plugins.
    /// </summary>
    /// <param name="sender">The control that is sending this event.</param>
    /// <param name="plugin">The plugin.</param>
    public delegate void OnPluginSelectedChanged(
        PluginControl sender, 
        IPlugin plugin);

    /// <summary>
    /// A control which displays plugins from a PluginSource in a CheckedListBox.
    /// </summary>
    public partial class PluginControl : UserControl
    {
        /// <summary>
        /// Called when a plugin is checked.
        /// </summary>
        public event OnPluginSelectedChanged PluginSelected;

        /// <summary>
        /// Called when a plugin is unchecked.
        /// </summary>
        public event OnPluginSelectedChanged PluginRemoved;

        private Dictionary<string, IPlugin> availablePlugins =
            new Dictionary<string, IPlugin>();

        Dictionary<string, int> positions = new Dictionary<string, int>();

        /// <summary>
        /// Constructor for PluginControl.
        /// </summary>
        public PluginControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Sets the plugin source for the control.
        /// </summary>
        /// <param name="pluginSource">The new plugin source.</param>
        public void SetPluginSource(
            IPluginSource pluginSource)
        {
            // Add all the available plugins to the list box, and store positions.
            this.checkedListBox1.Items.Clear();
            this.availablePlugins.Clear();
            foreach (IPlugin plugin in pluginSource.AvailablePlugins)
            {
                this.positions[plugin.ID] = 
                    this.checkedListBox1.Items.Add(new PluginWrapper(plugin));
                this.availablePlugins[plugin.ID] = plugin;
            }
        }

        /// <summary>
        /// Checks the given plugin IDs. Fires the PluginSelected event based on
        /// the given boolean.
        /// </summary>
        /// <param name="selectedPlugins">A list of plugin IDs to load.</param>
        /// <param name="fireSelected">
        /// Whether to fire the PluginSelected event.
        /// </param>
        public void SelectPlugins(
            IList<string> selectedPlugins,
            bool fireSelected)
        {
            // Calculate the order in which to load plugins.
            Queue<IPlugin> schedule =
                PluginTools.ScheduleDependencyLoading(
                    availablePlugins,
                    new Stack<string>(selectedPlugins));

            // Loop throught the plugins and load them in order.
            while (schedule.Count != 0)
            {
                this.SelectPlugin(schedule.Dequeue(), fireSelected);
            }
        }

        /// <summary>
        /// Checks the given plugin. Fires the PluginSelected event based on the 
        /// given boolean.
        /// </summary>
        /// <param name="plugin">The plugin to select.</param>
        /// <param name="fireSelected">
        /// Whether to fire the PluginSelected event.
        /// </param>
        public void SelectPlugin(
            IPlugin plugin,
            bool fireSelected)
        {
            if (!fireSelected)
            {
                this.checkedListBox1.ItemCheck -= 
                    new ItemCheckEventHandler(this.checkedListBox1_ItemCheck);
            }
            this.checkedListBox1.SetItemCheckState(
                this.checkedListBox1.FindString(plugin.DisplayName),
                CheckState.Checked);
            if (!fireSelected)
            {
                this.checkedListBox1.ItemCheck += 
                    new ItemCheckEventHandler(this.checkedListBox1_ItemCheck);
            }
        }

        /// <summary>
        /// Unchecks the given plug-in and fires the PluginRemoved event.
        /// </summary>
        /// <param name="plugin">The plugin to remove.</param>
        /// <param name="fireRemoved">
        /// Whether to fire the PluginRemoved event.
        /// </param>
        public void RemovePlugin(
            IPlugin plugin,
            bool fireRemoved)
        {
            if (!fireRemoved)
            {
                this.checkedListBox1.ItemCheck -=
                    new ItemCheckEventHandler(this.checkedListBox1_ItemCheck);
            }
            PluginWrapper wrapper = new PluginWrapper(plugin);
            
            this.checkedListBox1.SetItemCheckState(
                this.checkedListBox1.FindString(plugin.DisplayName),
                CheckState.Unchecked);
            if (!fireRemoved)
            {
                this.checkedListBox1.ItemCheck +=
                    new ItemCheckEventHandler(this.checkedListBox1_ItemCheck);
            }
        }

        /// <summary>
        /// Called when a plugin is checked in the list box.
        /// </summary>
        /// <param name="sender">The list box.</param>
        /// <param name="e">The event arguments.</param>
        private void checkedListBox1_ItemCheck(
            object sender,
            ItemCheckEventArgs e)
        {
            PluginWrapper wrapper =
                (PluginWrapper)this.checkedListBox1.Items[e.Index];

            if (e.NewValue == CheckState.Checked && this.PluginSelected != null)
            {
                Stack<string> selected = new Stack<string>();
                selected.Push(wrapper.Plugin.ID);
                Queue<IPlugin> pluginsToLoad =
                    PluginTools.ScheduleDependencyLoading(this.availablePlugins, selected);

                // Loop throught the plugins and load them in order.
                while (pluginsToLoad.Count != 0)
                {
                    IPlugin plugin = pluginsToLoad.Dequeue();
                    if (plugin.ID != wrapper.Plugin.ID)
                    {
                        if (!this.checkedListBox1.GetItemChecked(this.positions[plugin.ID]))
                        {
                            this.checkedListBox1.SetItemChecked(this.positions[plugin.ID], true);
                        }
                    }
                }

                this.PluginSelected(this, wrapper.Plugin);
            }
            else if (e.NewValue == CheckState.Unchecked && this.PluginRemoved != null)
            {
                this.PluginRemoved(this, wrapper.Plugin);
            }
        }

        /// <summary>
        /// A wrapper class that overrides ToString() to provide 
        /// IPlugin.DisplayName to the list box.
        /// </summary>
        private class PluginWrapper
        {
            /// <summary>
            /// The wrapped plugin.
            /// </summary>
            private IPlugin plugin;

            /// <summary>
            /// Constructor for PluginWrapper.
            /// </summary>
            /// <param name="plugin">The plugin to wrap.</param>
            public PluginWrapper(
                IPlugin plugin)
            {
                this.plugin = plugin;
            }

            /// <summary>
            /// The wrapped plugin.
            /// </summary>
            public IPlugin Plugin
            {
                get
                {
                    return this.plugin;
                }
            }

            /// <summary>
            /// Returns IPlugin.DisplayName.
            /// </summary>
            /// <returns>IPlugin.DisplayName.</returns>
            public override string ToString()
            {
                return this.plugin.DisplayName;
            }
        }
    }
}
